﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Runtime.Serialization;
using System;
using System.ComponentModel;
using System.Text;
using System.Threading;
using BrainTechLLC.ThreadSafeObjects;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;

namespace BrainTechLLC
{
    /// <summary>
    /// Abstract base class used to easily create other ILogger implementations
    /// </summary>    
    [Serializable]
    [DataContract]
    public abstract class LoggerBase : ILogger
    {
        /// <summary>
        /// Stores log lines waiting to be output
        /// </summary>
        protected ThreadSafeQueue<LogMessage> _logOutput;
        public event EventHandler<ObjectEventArgs<LogMessage>> LoggedMessage;

        protected LogSeverity _logFilter;
        protected ThreadSafeList<ILogListener> _logListeners;
        public LogSeverity LogFilter { get { return _logFilter; } }
        public bool WantExit { get; set; }

        public LoggerBase()
        {
            _logOutput = new ThreadSafeQueue<LogMessage>();
            _logListeners = new ThreadSafeList<ILogListener>();
            _logFilter = LogSeverity.Information;            
        }

        protected virtual void EnqueueNewLogLine(LogMessage logLine) { _logOutput.Enqueue(logLine); }

        public virtual void BeginLogging()
        {
            ThreadStart ts = new ThreadStart(LogListener);
            Thread logThread = ts.CreateAndRunThread();
        }

        protected abstract void LogListener();

        /// <summary>
        /// Lets the programmer turn on or off logging of specific severity levels
        /// </summary>
        /// <param name="severity"></param>
        /// <param name="logThisSeverityLevel"></param>
        public void SetFilter(LogSeverity severity, bool logThisSeverityLevel)
        {
            _logFilter = severity;
        }

        public bool AddLogListener(ILogListener listener) { if (_logListeners.Find(listener) != null) return false; _logListeners.Add(listener); return true; }
        public bool RemoveLogListener(ILogListener listener) { return _logListeners.Remove(listener); }

        public void Log(LogMessage message)
        {
            if (message.Severity >= _logFilter)
            {
                DateTime now = DateTime.Now;
                string nowString = now.ToString();
                
                //logLine = FormatLogEntry(nowString, message.MessageType, message, message.Severity);

                EnqueueNewLogLine(message);

                SimpleEventArgs<LogMessage> eventArgs = null;

                for (int n = 0; n < _logListeners.Count; n++)
                {
                    ILogListener listener = _logListeners.GetItem(n);
                    if (listener == null) break;
                    if (listener.IsListening)
                    {
                        if (eventArgs == null) eventArgs = new SimpleEventArgs<LogMessage>(message);
                        listener.OnNewLogEntry(eventArgs);
                    }
                }

                LoggedMessage.RaiseEvent(this, message);
            }
        }

        public void Log(string messageType, string message, LogSeverity severity)
        {
            LogMessage logMessage = new LogMessage()
            {
                LogTime = DateTime.Now,
                MessageType = messageType,
                Message = messageType,
                Severity = severity,
            };

            Log(logMessage);
        }

        public void Log(Exception ex, LogSeverity severity, bool verbose)
        {
            Log("Exception", (verbose) ? ex.ToString() : ex.Message, severity);
        }

        public void Log(Exception ex, string message, LogSeverity severity, bool verbose)
        {
            Log("Exception: " + message, (verbose) ? ex.ToString() : ex.Message, severity);
        }

        private string FormatLogEntry(string nowString, string messageType, string message, LogSeverity severity)
        {
            StringBuilder sb = new StringBuilder(256);
            sb.Append(nowString);
            sb.Append(',');
            sb.Append(messageType);
            sb.Append(',');
            sb.Append(severity.ToString());
            sb.Append(',');
            sb.Append(message);
            return sb.ToString();
        }
    }

    /// <summary>
    /// Provides notification of new log entries - used to listen to a log in realtime
    /// </summary>    
    [Serializable]
    [DataContract]
    public class LogListener : ILogListener
    {
#if SILVERLIGHT
#else
        [field: NonSerialized]
#endif
        public event EventHandler<SimpleEventArgs<LogMessage>> NewLogEntry;

        public void OnNewLogEntry(SimpleEventArgs<LogMessage> eventArgs)
        {
            if (NewLogEntry != null)
            {
                NewLogEntry(this, eventArgs);
            }
        }

        public bool IsListening
        {
            get { return (NewLogEntry != null); }
            set
            {
                if (value == false)
                {
                    if (NewLogEntry != null)
                    {
                        NewLogEntry -= NewLogEntryReceived;
                    }
                }
                else
                {
                    if (NewLogEntry == null)
                    {
                        NewLogEntry += new EventHandler<SimpleEventArgs<LogMessage>>(NewLogEntryReceived);
                    }
                }
            }
        }

        public virtual void NewLogEntryReceived(object sender, SimpleEventArgs<LogMessage> args)
        {
            Debug.WriteLine(args.Value);
        }
    }

    /// <summary>
    /// Implementation of ILogger that logs messages to memory and calls a delegate whenever a new log entry is written
    /// </summary>    
    [Serializable]
    [DataContract]
    public class OutputLogger : LoggerBase, ILogger
    {
        public Action<string> WriteOutput { get; set; }

        public static void CreateAndRegisterOutputLogger(Action<string> writeOutputDel)
        {
            OutputLogger logger = new OutputLogger(writeOutputDel);
            logger.BeginLogging();
            GlobalLogger.RegisterLogger(logger);
        }

        public OutputLogger(Action<string> writeOutputDel)
            : base()
        {
            WriteOutput = writeOutputDel;
        }

        protected override void LogListener()
        {
            List<LogMessage> logEntries = new List<LogMessage>();

            try
            {
                while (!WantExit)
                {
                    while (!WantExit && _logOutput.Count == 0) { Thread.Sleep(40); }

                    LogMessage logEntry;

                    while (!WantExit && _logOutput.Dequeue(out logEntry))
                    {
                        WriteOutput(logEntry.ToString());
                    }
                }
            }
            catch (Exception)
            {
            }
        }
    }

    /// <summary>
    /// Logs messages (using an implementation of ILogger)
    /// Provides global access to / sets the implementation of the logger 
    /// </summary>
    public static class GlobalLogger
    {
        private static ILogger _logger;

        public static ILogger Logger
        {
            get { return _logger; }
        }

        public static void Log(string messageType, string message, LogSeverity severity)
        {
            if (_logger != null)
            {
                _logger.Log(messageType, message, severity);
            }
        }

        public static void Log(this Exception ex, LogSeverity severity, bool verbose)
        {
            if (_logger != null)
            {
                _logger.Log(ex, severity, verbose);
            }
        }

        public static void Log(this Exception ex, string message, LogSeverity severity, bool verbose)
        {
            if (_logger != null)
            {
                _logger.Log(ex, message, severity, verbose);
            }
        }

        public static void RegisterLogger(ILogger logger)
        {
            _logger = logger;
        }
    }

    /// <summary>
    /// Provides buffered logging to a file
    /// </summary>
    [Serializable]
    [DataContract]
    public class FileLogger : LoggerBase, ILogger
    {
        private readonly int LogEntriesBeforeWrite = 1;
        private readonly int LogEntriesPerFile = 10000;

        private int _currentLogNumber;
        private string _currentLogFileNameCached;
        private int _entriesWrittenToCurrentLogFile;

        public string FileNameBase { get; set; }

        public static void CreateAndRegisterFileLogger(string path, string fileNameBase)
        {
            if (path.EndsWith("\\"))
                path = path.Substring(0, path.Length - 1);

            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);

            FileLogger logger = new FileLogger(path + "\\" + fileNameBase);
            logger.BeginLogging();
            GlobalLogger.RegisterLogger(logger);
        }

        public FileLogger()
            : base()
        {
        }

        public FileLogger(string fileNameBase)
            : this()
        {
            FileNameBase = fileNameBase;
        }

        public string CurrentLogFileName
        {
            get
            {
                if (_currentLogFileNameCached == null)
                    _currentLogFileNameCached = FileNameBase + _currentLogNumber.ToString() + ".txt";

                return _currentLogFileNameCached;
            }
        }

        protected void IncrementCurrentLogFileNumber()
        {
            _currentLogNumber++;
            _currentLogFileNameCached = null;
        }

        protected void MoveToNextFreeLogFileNumber()
        {
            while (!WantExit && File.Exists(CurrentLogFileName)) { IncrementCurrentLogFileNumber(); }
        }

        protected override void LogListener()
        {
            MoveToNextFreeLogFileNumber();
            List<LogMessage> logEntries = new List<LogMessage>();

            try
            {
                while (!WantExit)
                {
                    while (!WantExit && _logOutput.Count < LogEntriesBeforeWrite) { Thread.Sleep(100); }

                    while (!WantExit && _logOutput.DequeueMultiple(logEntries, LogEntriesBeforeWrite) > 0)
                    {
                        if (_entriesWrittenToCurrentLogFile >= LogEntriesPerFile) { IncrementCurrentLogFileNumber(); }
                        WriteLogEntries(logEntries);
                        logEntries.Clear();
                    }
                }
            }
            catch (ThreadAbortException)
            {
                Thread.CurrentThread.IsBackground = false;
                while (_logOutput.DequeueMultiple(logEntries, LogEntriesBeforeWrite) > 0)
                {
                    WriteLogEntries(logEntries);
                    logEntries.Clear();
                }
                Thread.CurrentThread.IsBackground = true;
            }
            catch (Exception exOther)
            {
                Console.WriteLine(exOther.ToString());
            }
            finally
            {
                Thread.CurrentThread.IsBackground = false;
                while (_logOutput.DequeueMultiple(logEntries, LogEntriesBeforeWrite) > 0)
                {
                    WriteLogEntries(logEntries);
                    logEntries.Clear();
                }
                Thread.CurrentThread.IsBackground = true;
            }
        }

        protected void WriteLogEntries(List<LogMessage> logEntries)
        {
            try
            {
                using (StreamWriter sw = new StreamWriter(CurrentLogFileName, true))
                {
                    logEntries.ForEach(logEntry => sw.WriteLine(logEntry.ToString()));
                    _entriesWrittenToCurrentLogFile += logEntries.Count;
                    sw.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
}
